/**
 * 
 */
package com.google.code.cisx.store.jpa;

import com.google.code.cisx.model.CommonUpdatableEntity;
import com.google.code.cisx.store.api.EntityNotFoundException;
import com.google.code.cisx.store.api.Query;
import com.google.code.cisx.store.api.Store;
import com.google.code.cisx.store.api.StoreException;
import com.google.code.cisx.store.tx.annotation.Transactional;
import com.google.inject.Inject;
import com.google.inject.Provider;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;

/**
 * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
 * @version $Id: JpaStore.java 258 2008-11-29 06:23:51Z rahul.thakur.xdev $
 * @since 1.0
 */
public class JpaStore<T extends CommonUpdatableEntity, Q extends Query<T>> implements Store<T, Q> {

  /**
   * {@link Provider} implementation that abstracts away the sourcing of an
   * {@link EntityManager} instance.
   */
  @Inject
  private Provider<EntityManager> entityManagerProvider;

  /**
   * @{inheritDoc
   * 
   * @see com.google.code.cisx.store.api.Store#delete(java.lang.Object)
   */
  @Transactional
  public void delete(T entity) throws StoreException {
    this.entityManagerProvider.get().remove(entity);
  }

  /**
   * @{inheritDoc
   * 
   * @see com.google.code.cisx.store.api.Store#lookup(Class, java.lang.Long)
   */
  public T lookup(Class<T> klass, Long id) throws StoreException, EntityNotFoundException {
    if (id == null)
      throw new EntityNotFoundException();
    T obj = this.entityManagerProvider.get().find(klass, id);
    if (obj == null)
      throw new EntityNotFoundException(String.format("Unable to locate %s instance for id %s", klass.getSigners(), id));
    return obj;
  }

  /**
   * @{inheritDoc
   * 
   * @see com.google.code.cisx.store.api.Store#query(org.apache.maven.continuum.store.api.Query,
   *      long, long)
   */
  @SuppressWarnings("unchecked")
  public List<T> query(Q query, int startIdx, int range) throws StoreException {
    Map<String, Object> where = new HashMap<String, Object>();
    String q = query.toString(where);
    List<T> results = find(q, where, startIdx, range);
    return results;
  }

  /**
   * @{inheritDoc
   * 
   * @see com.google.code.cisx.store.api.Store#save(java.lang.Object)
   */
  @Transactional
  public T save(T entity) throws StoreException {
    EntityManager em = this.entityManagerProvider.get();
    if (null != entity) {
      if (null == entity.getId())
        em.persist(entity);
      else
        entity = em.merge(entity);
    }    
    return entity;
  }

  /**
   * Prepares and executes a query using the 'where' criteria, a start index and
   * a given range of results to return.
   * 
   * @param queryString
   * @param whereParams
   * @param startIndex
   * @param range
   * @return
   * @throws DataAccessException
   */
  protected List find(final String queryString, final Map<String, Object> whereParams, final int startIndex,
      final int range) {
    javax.persistence.Query query = this.entityManagerProvider.get().createQuery(queryString);
    if (whereParams != null) {
      for (Iterator it = whereParams.entrySet().iterator(); it.hasNext();) {
        Map.Entry<String, Object> entry = (Map.Entry<String, Object>) it.next();
        query.setParameter(entry.getKey(), entry.getValue());
      }
    }
    if (startIndex > 0) {
      query.setFirstResult(startIndex);
    }
    if (range > 0) {
      query.setMaxResults(range);
    }
    return query.getResultList();
  }

}
